#include "session.h"
#include "tcpconnection.h"

namespace apollo
{
	namespace core
	{
		Session::Session(QSharedPointer<ConnectionConfig> config, 
				QSharedPointer<WireFormat> format, QObject *parent)
			: QThread(parent),messageId(1), sessionId(-1), config(config), format(format)
		{

		}

		Session::~Session()
		{
			try{
			QLOG_INFO() << "Session is destroyed.";
			}catch(...){}
		}

		/*void Session::start()
		{
			
			
		}*/

		MessagePtr Session::connect(MessagePtr loginRequest)
		{
			if(this->connection)
			{
				this->connection.clear();
			}
			state.state = SessionState::Disconnected;
			QSharedPointer<TcpConnection> c(new TcpConnection(config, format));
			PayloadPtr request(new Payload);
			
			request->setBody(loginRequest.data());
			request->setMessageId(messageId.fetchAndAddOrdered(1));
			request->setMessageType(Payload::MESSAGE_TYPE_APP);
		    PayloadPtr response = c->connect(request);
			if(response->getMessageType() != 33333)
			{
				this->connection = c;
				this->connection->moveToThread(this);
				QObject::connect(c.data(), SIGNAL(dataArrived(PayloadPtr)), this, SLOT(onDataArrived(PayloadPtr)));
				QObject::connect(c.data(), SIGNAL(errorHappened(const ConnectionErrorInfo &)), this, SLOT(onErrorHappened(const ConnectionErrorInfo &)));
				QObject::connect(c.data(), SIGNAL(stateChanged(const SessionState &)), this, SLOT(onStateChanged(const SessionState &)));
				state.state = SessionState::Login;	
			}
			
			return response->getMessage();
		}


		void Session::close()
		{
			QLOG_INFO() << "Stopping session.";
			if(connection)
			{
				QObject::disconnect(connection.data(), SIGNAL(dataArrived(PayloadPtr)), this, SLOT(onDataArrived(PayloadPtr)));
				QObject::disconnect(connection.data(), SIGNAL(errorHappened(const ConnectionErrorInfo &)), this, SLOT(onErrorHappened(const ConnectionErrorInfo &)));
				QObject::disconnect(connection.data(), SIGNAL(stateChanged(const SessionState &)), this, SLOT(onStateChanged(const SessionState &)));
			}
			connection.clear();
			//QCoreApplication::postEvent(this, new QEvent(QEvent::Close));
			this->quit();
		}

		void Session::run()
		{
			QLOG_INFO() << "Starting session.";
			messageId = 1;
			
			this->exec();
			QLOG_INFO() << "Stopped session.";
			//deleteLater();
			
		}

		void Session::customEvent(QEvent * event)
		{
			if(event->type() == QEvent::Close)
			{
				this->quit();
			}
		}
		void Session::write(PayloadPtr payload)
		{
			quint32 id = messageId.fetchAndAddOrdered(1);
			payload->setMessageId(id);
			connection->write(payload);
		}
		void Session::processLogin(PayloadPtr loginResponse)
		{
			if(state.state == SessionState::Logining)
			{
				state.state = SessionState::Login;
				emit stateChanged(state);

			}
			else
			{
				QLOG_ERROR() << "Received Login Respose but not request.";
			}
		}
		void Session::processConnected()
		{
			//login();
		}
		void Session::processDisconnected()
		{
		}
		void Session::login()
		{
			QLOG_INFO() <<  "begin to login";
			
			if(provider != NULL)
			{
				PayloadPtr payload = provider();
				payload->setMessageId(Payload::MESSAGE_TYPE_LOGIN);
				if(sessionId.isNull() == false)
				{
					payload->addHeader(Payload::SESSION_ID, sessionId);
				}
				state.state = SessionState::Logining;
				emit stateChanged(state);
				write(payload);
				QTimer::singleShot(config->loginTimeout, this, SLOT(onLogintTimeout()));
			}
		}

		void Session::onLoginTimeout()
		{
			if(state.state == SessionState::Logining)
			{
				//abort connection, close connection
				connection->disconnect();
				emit errorHappened(ConnectionErrorInfo( "LoginTimeout","Login Timeout", ConnectionErrorInfo::ERROR_TYPE_SESSION));
			}
		}

		void Session::onDataArrived(PayloadPtr payload)
		{

			/*QLOG_TRACE() << "<< Received: id = " << payload->getMessageId() << "type:" 
				<< payload->getMessageType() << "header:" << payload->getHeader() << "Body:"
				<<  Utils::toHexRepresentation(payload->getBody(), payload->getBodySize());*/
			QLOG_TRACE() << payload;

			if(payload->getMessageId() == Payload::MESSAGE_TYPE_APP)
			{
				emit dataArrived(payload);
			}
			else if(payload->getMessageId() == Payload::MESSAGE_TYPE_LOGIN_SUCCESS 
				|| payload->getMessageId() == Payload::MESSAGE_TYPE_LOGIN_ERROR)
			{
				processLogin(payload);
			}
			else if(payload->getMessageId() == Payload::MESSAGE_TYPE_HELLO) 
			{
				//TODO: process heartbeat

			}

		}

		void Session::onStateChanged(const SessionState &state)
		{
			QLOG_INFO() << "State:" << state.schema << state.ip << ":" << state.port << state.state;
			//SessionState oldState = this->state.state;
			this->state = state;

			if(state.state == SessionState::Connected)
			{
				processConnected();
			}
			else if(state.state == SessionState::Disconnected)
			{
				processDisconnected();
			}

			emit stateChanged(this->state);
		}

		void Session::onErrorHappened(const ConnectionErrorInfo &error)
		{
			QLOG_INFO() << "Error:" << error.errorType <<error.errorCode << error.errorDesc;
			emit errorHappened(error);
		}
	}

}
